/*
 * @author Nate Morse
 * copyright (c) Nate Morse 2008
 * graphspace.org/viewer/raphael/graphspace.js
 * This file holds functions that render a GS graph as a raphael SVG/VML element in a browser.
 * Uses the raphael.js version 0.5.4 vector graphics library with some modifications to rotate.
 */

String.prototype.trim = function () {
	return this.replace(/^\s*/, "").replace(/\s*$/, "");
}

function GraphRenderer(args, paper){
	this.g = args['graph'];
	this.c = paper;
	this.defaultNodeShape = (this.g.view.defaultNode.shape != null) ? this.g.view.defaultNode.shape : "roundRect";
	this.defaultNodePadding = (this.g.view.defaultNode.padding != null) ? this.g.view.defaultNode.padding : 4;
	this.defaultNodeFill = (this.g.view.defaultNode.fill != null) ? this.g.view.defaultNode.fill : "#CCCCFF";
	this.defaultNodeWidth = (this.g.view.defaultNode.width != null) ? this.g.view.defaultNode.width : 150;
	this.defaultNodeOpacity = (this.g.view.defaultNode.opacity != null) ? this.g.view.defaultNode.opacity : 1.0;
	this.defaultEdgeArrow = (this.g.view.defaultEdge && this.g.view.defaultEdge.arrow != null) ? this.g.view.defaultEdge.arrow : true;
	this.defaultEdgeMagnitude = (this.g.view.defaultEdge && this.g.view.defaultEdge.mag != null) ? this.g.view.defaultEdge.mag :30;

}

GraphRenderer.prototype.renderGraph=function renderGraphf(){
	for (var node_id in this.g.nodes) {
		var node = this.g.nodes[node_id];
		if (node.graphic == null && node.view.display != 'none') { // && node.view.display != 'none'
			node.graphic = this.renderNode(node_id, node);
		}
		for (edge_id in node.edges) {
			var edge = node.edges[edge_id];
			if (edge.view.display != 'none') {
				var node2 = this.g.nodes[edge.toNode];
				if (node2.graphic == null && node2.view.display != 'none') { // && node2.view.display != 'none'
					node2.graphic = this.renderNode(edge.toNode, node2);
				}
				if (edge.graphic == null) {
					var fromCP = this.getConnectionPoint("tail", node, edge);
					var toCP = this.getConnectionPoint("head", node2, edge);
					edge.graphic = this.renderEdge(fromCP, toCP, edge);
				}
			}
		}
	}
	for (node_id in this.g.nodes) {
		var node = this.g.nodes[node_id];
		if (node.graphic != null && node.graphic.elements != null && node.view.display != 'none') {
			for (var i = 0; i < node.graphic.elements.length; i++) {
				var element;
				element = node.graphic.elements[i];
				element.toFront();
			}
		}
	}
}

GraphRenderer.prototype.getConnectionPoint=function getConnectionPointf(edge_end, node, edge){
	var mag = this.defaultEdgeMagnitude;
	var angle_in_deg = (edge_end == 'head') ? 180 : 0;
	var x = node.graphic.bb.x;
	var y = node.graphic.bb.y;
	var width = node.graphic.bb.width;
	var height = node.graphic.bb.height;
	var cx = x + (width / 2);
	var cy = y + (height / 2);
	var controlPointXOffset;
	var controlPointYOffset;
	var pointX;
	var pointY;
	if (edge.view && edge.view[edge_end]) {
		angle_in_deg = (edge.view[edge_end].angle != null) ? edge.view[edge_end].angle : angle_in_deg;
		mag = (edge.view[edge_end].mag != null) ? edge.view[edge_end].mag : mag;
	}
	var shape = (node.view.shape != null) ? node.view.shape : this.defaultNodeShape;
	if (shape == 'circle') {
		pointX = cx + Math.cos(angle_in_deg * 0.0174532925) * (width / 2);
		pointY = cy + Math.sin(angle_in_deg * 0.0174532925) * (width / 2);
	}
	else 
		if (shape == 'rect' || shape == 'roundRect') {
			// note: this is based on diagonal quadrants (not strict angle), -- better re-propotioning UI behavior (IMHO). 
			while (angle_in_deg < 0) {
				angle_in_deg += 360;
			}
			angle_in_deg = angle_in_deg % 360; // nomalize
			var lrx = x + width; // lower right x
			var lry = y + height; // lower right y
			var pad = (node.view.padding != null) ? node.view.padding : this.defaultNodePadding;
			if (angle_in_deg >= 45 && angle_in_deg < 135) { //bottom
				pointX = lrx - ((angle_in_deg - 45) / 90) * width;
				pointY = lry;
				if (shape == 'roundRect' && pad > 6) {
					if (pointX < (x + pad)) { // left
						var proportion = (pointX - x) / pad;
						var angle = 135 - (proportion * 45);
						pointX = x + pad + Math.cos(angle * 0.0174532925) * pad;
						pointY = (lry - pad) + Math.sin(angle * 0.0174532925) * pad;
					}
					else 
						if (pointX > (lrx - pad)) { // right
							var proportion = (pointX - lrx) / pad;
							var angle = 45 - (proportion * 45);
							pointX = (lrx - pad) + Math.cos(angle * 0.0174532925) * pad;
							pointY = (lry - pad) + Math.sin(angle * 0.0174532925) * pad;
						}
				}
			}
			else 
				if (angle_in_deg >= 135 && angle_in_deg < 225) { //left
					pointX = x;
					pointY = (y + height) - ((angle_in_deg - 135) / 90) * height;
					if (shape == 'roundRect' && pad > 6) {
						if (pointY < (y + pad)) { //  top
							var proportion = (pointY - y) / pad;
							var angle = 225 - (proportion * 45);
							pointX = x + pad + Math.cos(angle * 0.0174532925) * pad;
							pointY = y + pad + Math.sin(angle * 0.0174532925) * pad;
						}
						else 
							if (pointY > (lry - pad)) { //  bottom
								var proportion = (pointY - lry) / pad;
								var angle = 135 - (proportion * 45);
								pointX = x + pad + Math.cos(angle * 0.0174532925) * pad;
								pointY = (lry - pad) + Math.sin(angle * 0.0174532925) * pad;
							}
					}
				}
				else 
					if (angle_in_deg >= 225 && angle_in_deg < 315) { //top
						pointX = x + ((angle_in_deg - 225) / 90) * width;
						pointY = y;
						if (shape == 'roundRect' && pad > 6) {
							if (pointX < (x + pad)) { // left
								var proportion = (pointX - x) / pad;
								var angle = 225 + (proportion * 45);
								pointX = x + pad + Math.cos(angle * 0.0174532925) * pad;
								pointY = y + pad + Math.sin(angle * 0.0174532925) * pad;
							}
							else 
								if (pointX > (lrx - pad)) { // right
									var proportion = (pointX - lrx) / pad;
									var angle = 315 + (proportion * 45);
									pointX = (lrx - pad) + Math.cos(angle * 0.0174532925) * pad;
									pointY = y + pad + Math.sin(angle * 0.0174532925) * pad;
								}
						}
					}
					else { // right
						pointX = lrx;
						pointY = y + ((angle_in_deg + 45) % 360 / 90) * height;
						if (shape == 'roundRect' && pad > 6) {
							if (pointY < (y + pad)) { //  top
								var proportion = (pointY - y) / pad;
								var angle = 315 + (proportion * 45);
								pointX = (lrx - pad) + Math.cos(angle * 0.0174532925) * pad;
								pointY = y + pad + Math.sin(angle * 0.0174532925) * pad;
							}
							else 
								if (pointY > (lry - pad)) { //  bottom
									var proportion = (pointY - lry) / pad;
									var angle = 45 + (proportion * 45);
									pointX = (lrx - pad) + Math.cos(angle * 0.0174532925) * pad;
									pointY = (lry - pad) + Math.sin(angle * 0.0174532925) * pad;
								}
						}
					}
		}
		else {
			pointX = cx;
			pointY = cy;
		}
	
	controlPointXOffset = Math.cos(angle_in_deg * 0.0174532925) * mag;
	controlPointYOffset = Math.sin(angle_in_deg * 0.0174532925) * mag;
	return [{
		x: pointX,
		y: pointY
	}, {
		x: pointX + controlPointXOffset,
		y: pointY + controlPointYOffset
	}]
}

GraphRenderer.prototype.renderEdge=function renderEdgef(from, to, edge){
	// TODO implement Knots, edge.knots ...
	var line = this.c.path({
		stroke: "#a35"
	}).moveTo(from[0].x, from[0].y).curveTo(from[1].x, from[1].y, to[1].x, to[1].y, to[0].x, to[0].y);
	if (to[1].x != to[0].x || to[1].y != to[0].y) {
		var dx = to[0].x - to[1].x;
		var dy = to[0].y - to[1].y;
		// draw arrow head
		var angle = this.angleFromDelta(dx, dy);
		//alert(dx + " " + dy + " " + angle + " ");
		var arrow = (edge.view.arrow != null)? edge.view.arrow: this.defaultEdgeArrow;
		if (arrow) {
			arrow = this.c.path({
				fill: "#a35"
			}).moveTo(0, 0).lineTo(-8, -4).lineTo(-8, 4).andClose();
			//drawArrow(to[0].x, to[0].y, a);
			//relatively absolutely around (0.0)
			arrow.translate(to[0].x, to[0].y).rotate({
				degree: angle,
				pivotX: 0,
				pivotY: 0
			});
		}
	}
	return {
		elements: [line, arrow]
	};
}

GraphRenderer.prototype.angleFromDelta=function angleFromDeltaf(x, y){
	if (Math.abs(x) < Math.pow(10, -6)) {
		if (Math.abs(y) < Math.pow(10, -6)) 
			return 0;
		if (y > 0) 
			return 90;
		if (y < 0) 
			return 270;
	}
	if (x > 0) {
		if (y >= 0) 
			return 180 * Math.atan(y / x) / Math.PI;
		if (y < 0) 
			return 180 * Math.atan(y / x) / Math.PI - (-1) * 360;
	}
	if (x < 0) {
		if (y >= 0) 
			return 180 - 180 * Math.atan(-y / x) / Math.PI;
		if (y < 0) 
			return -180 - (-1) * 180 * Math.atan(y / x) / Math.PI - (-1) * 360;
	}
}

GraphRenderer.prototype.renderNode=function renderNodef(id, node){
	var pad = (node.view.padding != null) ? node.view.padding : this.defaultNodePadding;
	var fill = (node.view.fill != null) ? node.view.fill : this.defaultNodeFill;
	var width = (node.view.width != null) ? node.view.width : this.defaultNodeWidth;
	var opacity = (node.view.opacity != null) ? node.view.opacity : this.defaultNodeOpacity;
	var shape = (node.view.shape != null) ? node.view.shape : this.defaultNodeShape;
	var labelText = (node.view.label != null) ? node.view.label : id;
	if(labelText.trim() == "") {
		var textObj = new Object();
		textObj.elements = [];
		var height = (node.view.height != null) ? node.view.height : this.defaultNodeWidth;
		var textBB = {x:node.view.x, y:node.view.y, width:width, height:height};
	}
	else {
		var textObj = this.wrapText(node.view.x, node.view.y, pad, labelText, width);
		var textBB = textObj.bb;
	}
	//alert (textBB.width);
	if ((textBB.x == 0 &&
	textBB.width == 0 &&
	textBB.y == 0 &&
	textBB.height == 0) ||
	(textBB.x == 1 &&
	textBB.width > 500 &&
	textBB.y == 1 &&
	textBB.height > 400)) {
		textBB.x = node.view.x;
		textBB.width = width;
		textBB.y = node.view.y;
		textBB.height = 30;
	}
	var elements = [];
	if (shape == 'circle') {
		var r = this.getRadius(textBB, pad)
		var cx = textBB.x + (textBB.width / 2);
		var cy = textBB.y + (textBB.height / 2);
		var shapeElement = this.c.circle(cx, cy, r);
		if (node.nodeType == "input") {
			var inputElement = this.c.circle(cx, cy, r);
		}
	}
	else 
		if (shape == 'roundRect') {
			var shapeElement = this.c.rect(textBB.x - pad, textBB.y - pad, textBB.width + (pad * 2), textBB.height + (pad * 2), pad);
			if (node.nodeType == "input") {
				var inputElement = this.c.rect(textBB.x - pad, textBB.y - pad, textBB.width + (pad * 2), textBB.height + (pad * 2), pad);
			}
		}
		else {
			var shapeElement = this.c.rect(textBB.x - pad, textBB.y - pad, textBB.width + (pad * 2), textBB.height + (pad * 2));
			if (node.nodeType == "input") {
				var inputElement = this.c.rect(textBB.x - pad, textBB.y - pad, textBB.width + (pad * 2), textBB.height + (pad * 2));
			}
		}
	shapeElement.attr("fill", fill);
	shapeElement.attr("stroke", "#000");
	shapeElement.attr("opacity", opacity);
	
	if (node.nodeType == "input") {
		if (node.source == 'onclick') {
			var thisRenderer = this;
			inputElement[0].onclick = function(){	
				thisRenderer.processNode(id, new CheckList());
			};
			inputElement.attr({
				"fill-opacity":0
				,"fill":"#FFF"
				,"fill-opacity":0
				,"stroke-opacity":0.4
				,"stroke-width":6
				,"stroke":"#FF8"
			});
		}
		
	}
	
	elements.push(shapeElement);
	for (var i = 0; i < textObj.elements.length; i++) {
		element = textObj.elements[i];
		element.toFront();
		element.attr('opacity', opacity);
		elements.push(element);
	}
	if (node.nodeType == "input") {
		elements.push(inputElement);
	}
	var bb = shapeElement.getBBox();
	return {
		elements: elements,
		bb: bb
	}
}

GraphRenderer.prototype.wrapText=function wrapTextf(x, y, pad, text, desiredWidth){
	var words = String(text).split(" ");
	var phrase = "";
	var pervPhrase = "";
	var bb = null;
	var pervBB = null;
	var group = null;
	var lines = [];
	var t1;
	var width = 0;
	var height = 0;
	// get height with fill ascent and descent included.
	t1 = this.c.text(300, -50, 'by');
	bb = t1.getBBox();
	var heightOfLine = bb.height;
	t1.remove();
	t1 = null;
	bb = null;
	for (var i = 0; i < words.length; i++) {
		var word = words[i];
		phrase += word + " ";
		t1 = this.c.text(300, -50, phrase);
		prevBB = bb;
		bb = t1.getBBox();
		t1.remove();
		if (bb.width >= desiredWidth) {
			if (pervPhrase != "") {
				lines.push({
					phrase: pervPhrase,
					bb: prevBB
				});
				height += (heightOfLine + pad);
				phrase = word + " ";
				pervPhrase = word + " ";
			}
			else {
				lines.push({
					phrase: phrase,
					bb: bb
				});
				height += (heightOfLine + pad);
				phrase = "";
			}
		}
		else {
			pervPhrase = phrase;
			prevBB = bb;
		}
	}
	if (pervPhrase != "") {
		lines.push({
			phrase: pervPhrase,
			bb: prevBB
		});
		height += heightOfLine;
	}
	
	if (lines.length == 1) {
		var line = lines[0].phrase;
		var phraseWidth = lines[0].bb.width;
		var phraseHeight = heightOfLine;
		var e = this.c.text(x + pad + (phraseWidth / 2), y + pad + phraseHeight, line.trim());
		var ebb = e.getBBox();
		return {
			elements: [e],
			bb: ebb
		};
	}
	else {
		var accumY = y + pad;
		var elements = [];
		var e_i = 0;
		for (var i = 0; i < lines.length; i++) {
			var line = lines[i].phrase;
			var phraseWidth = lines[i].bb.width;
			var phraseHeight = heightOfLine;
			elements.push(this.c.text(x + pad + (phraseWidth / 2), accumY + phraseHeight, line.trim()));
			bb = elements[e_i].getBBox();
			accumY += heightOfLine + pad;
			width = Math.max(width, bb.width);
			e_i++;
		}
		return {
			elements: elements,
			bb: {
				x: x + pad,
				y: y + pad,
				width: width,
				height: height
			}
		};
	}
}

GraphRenderer.prototype.getRadius=function getRadiusf(bb, pad){
	var maxlen = Math.max(bb.width, bb.height) / 2;
	var minlen = Math.min(bb.width, bb.height) / 2;
	if (maxlen <= 2) {
		return 2 + pad;
	}
	if (minlen <= 1) {
		return maxlen + pad;
	}
	minlen *= minlen;
	maxlen *= maxlen;
	return Math.sqrt(maxlen + minlen) + pad;
}



GraphRenderer.prototype.processNode=function processNodef(nodeId, done) {
	//alert(this.g.nodes[nodeId].code[0]);
	var node = this.g.nodes[nodeId];
	var codes = node.code;
	if (node.data == null) {
		node.data = new Object();
	}
	for(var line in codes) {
		var code = this.convertToJS(nodeId, node, codes[line]);
		//alert(code);
		eval(code);
	}
	for(var datum in node.data) {
		if (node.edges != null && node.edges[datum] != null) {
			var edge = node.edges[datum];
			if (edge.edgeType == 'set') {
				this.doEdgeSet(node, edge, node.data[datum], done);
			}
		}
	}
}

GraphRenderer.prototype.convertToJS=function convertToJSf(nodeId, node, code_line) {
	return this._toJSStatmentString(code_line)
}

GraphRenderer.prototype._toJSStatmentString=function _toJSStatmentStringf(statement) {
	//alert(statement);
	st = statement.replace(/\!/g, ' node.');
	st = st.replace(/\[/g, ' node.data["');
	st = st.replace(/\]/g, '"]')+';';
	return st;
}

GraphRenderer.prototype.doEdgeSet=function doEdgeSetf(fromNode, edge, datum, done) {
	if (edge.edgeType == 'set') {
		var toNode = this.g.nodes[edge.toNode];
		if (edge.subType == 'view_attribute') {
			toNode.view[edge.alis] = datum;
			if (edge.alis == 'show') {
				if (datum == 0 && toNode.graphic != null) {
					for (var e in toNode.graphic.elements) {
						toNode.graphic.elements[e].remove();
						toNode.graphic.elements[e] = null;
					}
					toNode.graphic = null;
					for (var e in edge.graphic.elements) {
						edge.graphic.elements[e].remove();
						edge.graphic.elements[e] = null;
					}
					edge.graphic = null;
				}
				else if (datum == 1 && toNode.graphic == null) {
					toNode.graphic = this.renderNode(edge.toNode, toNode); 
					if (edge.graphic == null) {
						var fromCP = this.getConnectionPoint("tail", fromNode, edge);
						var toCP = this.getConnectionPoint("head", toNode, edge);
						edge.graphic = this.renderEdge(fromCP, toCP, edge);
					}
					for(var e in toNode.graphic.elements) {
						toNode.graphic.elements[e].toFront();
					}
				}
			}
			else if (toNode.graphic != null) {
				for(var e in toNode.graphic.elements) {
					toNode.graphic.elements[e].attr(edge.alis, datum);
				}
			}
		}
		else {
			toNode.data[edge.alis] = datum;
			if (!done.checked(edge.toNode)) {
				this.processNode(edge.toNode, done.list.push(fromNode));
			}
		}
		
	}
}

function CheckList(){
	this.list = [];
}
CheckList.prototype.checked=function checkedf(ele){
	for (e in this.list) {
		if (e == ele) 
			//alert(ele+" found ")
			return true;
	}
	//alert(ele+" NOT found ")
	return false;
}

